/*
 * This file is part of the openHiTLS project.
 *
 * openHiTLS is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "app_asn1parse.h"

#define MAX_BUFSIZE (4 * 1024)
#define BSL_BASE64_DEC_ENOUGH_LEN(len) (((len) + 3) / 4 * 3)

typedef enum Asn1OptType {
    HITLS_APP_OPT_ASN1_ERR = -1,
    HITLS_APP_OPT_ASN1_EOF = 0,
    HITLS_APP_OPT_ASN1_HELP = 1,
    HITLS_APP_OPT_ASN1_INFORM,
    HITLS_APP_OPT_ASN1_IN,
    HITLS_APP_OPT_ASN1_OUT,
    HITLS_APP_OPT_ASN1_INDENT,
    HITLS_APP_OPT_ASN1_DUMP,
    HITLS_APP_OPT_ASN1_OFFSET,
    HITLS_APP_OPT_ASN1_ITEM,
    HITLS_APP_OPT_ASN1_DLIMIT,
    HITLS_APP_OPT_ASN1_NOOUT
} HITLSOptType;

HITLS_CmdOption g_asn1Opts[] = {
    {"help", HITLS_APP_OPT_ASN1_HELP, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Display this function summary"},
    // I/O
    {"inform", HITLS_APP_OPT_ASN1_INFORM, HITLS_APP_OPT_VALUETYPE_FMT_PEMDER, "Input format, PEM or DER"},
    {"in", HITLS_APP_OPT_ASN1_IN, HITLS_APP_OPT_VALUETYPE_IN_FILE, "Input file"},
    {"out", HITLS_APP_OPT_ASN1_OUT, HITLS_APP_OPT_VALUETYPE_OUT_FILE, "Output file"},
    {"offset", HITLS_APP_OPT_ASN1_OFFSET, HITLS_APP_OPT_VALUETYPE_INT, "Offset to start parsing"},
    {"item", HITLS_APP_OPT_ASN1_ITEM, HITLS_APP_OPT_VALUETYPE_STRING, "ASN.1 item template name"},
    {"noout", HITLS_APP_OPT_ASN1_NOOUT, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "No output to produce"},
    // Formatting
    {"indent", HITLS_APP_OPT_ASN1_INDENT, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Indent value for output"},
    {"dump", HITLS_APP_OPT_ASN1_DUMP, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Dump data in hex format"},
    {"dlimit", HITLS_APP_OPT_ASN1_DLIMIT, HITLS_APP_OPT_VALUETYPE_INT, "Maximum dump length"},
    {NULL} // 选项列表结束标记
};

static int32_t ReadFileToBuf(const char *filename, BSL_ASN1_Buffer *buf);
int32_t DecodePemToAsn1(BSL_ASN1_Buffer *buffer);
int32_t DecodeBase64ToAsn1(BSL_ASN1_Buffer *buf);

int32_t HITLS_Asn1ParseMain(int argc, char **argv) {
    int32_t mainRet = HITLS_APP_SUCCESS;
    HITLSOptType optType;
    char *infile = NULL;
    char *outfile = NULL;
    uint32_t offset = 0;
    uint32_t indent = 0;
    uint32_t noout = 0;
    BSL_ParseFormat inForm = BSL_FORMAT_UNKNOWN;
    BSL_UIO *outUio=NULL;
    BSL_ASN1_Buffer asn1buffer = {
        .tag  = 0,
        .len  = 0,
        .buff = NULL};
    uint32_t dump = 0;
    uint32_t dlimit = 32;
    char* dtmp = NULL;
    HITLS_Asn1ItemInfo* item = NULL;

    mainRet = HITLS_APP_OptBegin(argc, argv, g_asn1Opts);
    if (mainRet != HITLS_APP_SUCCESS) {
        AppPrintError("asn1parse: error in opt begin.\n");
        goto end;
    }

    optType = HITLS_APP_OptNext();
    if (optType == HITLS_APP_OPT_ASN1_ERR || optType == HITLS_APP_OPT_ASN1_EOF) {
        AppPrintError("asn1parse: Use -help for summary.\n");
        mainRet = HITLS_APP_INVALID_ARG;
        goto end;
    }

    while (optType != HITLS_APP_OPT_ASN1_EOF) {
        switch (optType) {
            case HITLS_APP_OPT_ASN1_EOF:
            case HITLS_APP_OPT_ASN1_ERR:
                AppPrintError("asn1parse: Use -help for summary.\n");
                goto end;

            case HITLS_APP_OPT_ASN1_HELP:
                HITLS_APP_OptHelpPrint(g_asn1Opts);
                goto end;

            case HITLS_APP_OPT_ASN1_INFORM: {
                char *str = HITLS_APP_OptGetValueStr();
                mainRet = HITLS_APP_OptGetFormatType(str, HITLS_APP_OPT_VALUETYPE_FMT_PEMDER, &inForm);
                if (mainRet != HITLS_APP_SUCCESS) {
                    AppPrintError("asn1parse: Invalid format \"%s\" for -inform.\asn1parse: Use -help for summary.\n", str);
                    goto end;
                }
                break;
            }
            case HITLS_APP_OPT_ASN1_IN:
                infile = HITLS_APP_OptGetValueStr();
                if (infile == NULL || strlen(infile) >= PATH_MAX) {
                    AppPrintError("asn1parse: The length of infile error, range is (0, 4096).\n");
                    return HITLS_APP_OPT_VALUE_INVALID;
                }
                break;

            case HITLS_APP_OPT_ASN1_OUT:
                outfile = HITLS_APP_OptGetValueStr();
                if (outfile == NULL || strlen(outfile) >= PATH_MAX) {
                    AppPrintError("asn1parse: The length of outfile error, range is (0, 4096).\n");
                    return HITLS_APP_OPT_VALUE_INVALID;
                }
                break;

            
            case HITLS_APP_OPT_ASN1_INDENT:
                indent = 1;
                break;
            
            case HITLS_APP_OPT_ASN1_DUMP:
                dump = 1;
                break;

            case HITLS_APP_OPT_ASN1_NOOUT:
                noout = 1;
                break;

            //oid

            case HITLS_APP_OPT_ASN1_OFFSET:
                dtmp = HITLS_APP_OptGetValueStr();
                sscanf(dtmp, "%u", &offset);
                break;
            case HITLS_APP_OPT_ASN1_ITEM:
                item = LookupItemInfo(HITLS_APP_OptGetValueStr());
                if (item == NULL) {
                    AppPrintError("asn1parse: Unknown item template name.\n");
                    mainRet = HITLS_APP_INVALID_ARG;
                    goto end;
                }
                break;
            case  HITLS_APP_OPT_ASN1_DLIMIT:
                dtmp = HITLS_APP_OptGetValueStr();
                sscanf(dtmp, "%u", &dlimit);
                break;
            default:
                break;
        }
        optType = HITLS_APP_OptNext();
    }

    if (outfile && (outUio = HITLS_APP_UioOpen(outfile, 'w', 0)) == NULL) {
        AppPrintError("asn1parse: error in opening infile.\n");
        goto end;
    }
    if (noout == 0 && outUio == NULL) {
        outUio = HITLS_APP_UioOpen(NULL, 'w', 0);
    }

    mainRet = ReadFileToBuf(infile, &asn1buffer);
    if (mainRet != HITLS_APP_SUCCESS) {
        AppPrintError("asn1parse: error in read data from infile.\n");
        goto end;
    }

    if (inForm == BSL_FORMAT_PEM){
        mainRet = DecodePemToAsn1(&asn1buffer);
        if (mainRet != HITLS_APP_SUCCESS) {
            AppPrintError("asn1parse: error in parsing pem to asn1\n");
            goto end;
        }
    } else {
        mainRet = DecodeBase64ToAsn1(&asn1buffer);
        if (mainRet != HITLS_APP_SUCCESS) {
            AppPrintError("asn1parse: error in parsing der to asn1\n");
            goto end;
        }
    }
    
    if (offset >= asn1buffer.len) {
        AppPrintError("asn1parse: offset out of range\n");
        goto end;
    }

    uint32_t parseLen = asn1buffer.len - offset;

    if (item != NULL) {
        mainRet = Asn1ParseItem(outUio, item, asn1buffer.buff + offset, parseLen, dlimit, indent, dump);
    } else {
        mainRet = Asn1ParseDump(outUio, asn1buffer.buff + offset, parseLen, dlimit, indent, dump);
    }

end:
    BSL_SAL_Free(asn1buffer.buff);
    BSL_UIO_Free(outUio);
    HITLS_APP_OptEnd();
    return mainRet;
}

static int32_t ReadFileToBuf(const char *filename, BSL_ASN1_Buffer *buffer) {
    if (filename == NULL || buffer == NULL) {
        (void)AppPrintError("Invalid input parameters\n");
        return HITLS_APP_INVALID_ARG;
    }

    buffer->buff = NULL;
    buffer->len = 0;

    BSL_UIO *readUio = HITLS_APP_UioOpen(filename, 'r', 0);
    if (readUio == NULL) {
        (void)AppPrintError("Failed to open the file <%s>, No such file or directory\n", filename);
        return HITLS_APP_UIO_FAIL;
    }

    uint64_t fileTotalLen = 0;
    int32_t ret = BSL_UIO_Ctrl(readUio, BSL_UIO_PENDING, sizeof(fileTotalLen), &fileTotalLen);
    if (ret != BSL_SUCCESS) {
        BSL_UIO_SetIsUnderlyingClosedByUio(readUio, true);
        BSL_UIO_Free(readUio);
        (void)AppPrintError("Failed to obtain the file length\n");
        return HITLS_APP_UIO_FAIL;
    }

    if (fileTotalLen > UINT32_MAX) {
        BSL_UIO_SetIsUnderlyingClosedByUio(readUio, true);
        BSL_UIO_Free(readUio);
        (void)AppPrintError("File too large (exceeds maximum buffer size)\n");
        return HITLS_APP_UIO_FAIL;
    }

    buffer->buff = (uint8_t *)BSL_SAL_Malloc((uint32_t)fileTotalLen);
    if (buffer->buff == NULL) {
        BSL_UIO_SetIsUnderlyingClosedByUio(readUio, true);
        BSL_UIO_Free(readUio);
        (void)AppPrintError("Failed to allocate memory for file buffer\n");
        return HITLS_APP_MEM_ALLOC_FAIL;
    }

    uint32_t remaining = (uint32_t)fileTotalLen;
    uint8_t *currentPos = buffer->buff;

    while (remaining > 0) {
        uint32_t readSize = (remaining > MAX_BUFSIZE) ? MAX_BUFSIZE : remaining;
        uint32_t actualRead = 0;

        ret = BSL_UIO_Read(readUio, currentPos, readSize, &actualRead);
        if (ret != BSL_SUCCESS || actualRead != readSize) {
            BSL_SAL_FREE(buffer->buff);
            buffer->buff = NULL;
            buffer->len = 0;
            BSL_UIO_SetIsUnderlyingClosedByUio(readUio, true);
            BSL_UIO_Free(readUio);
            (void)AppPrintError("Failed to read file content\n");
            return HITLS_APP_UIO_FAIL;
        }

        currentPos += actualRead;
        remaining -= actualRead;
    }

    buffer->len = (uint32_t)fileTotalLen;

    BSL_UIO_SetIsUnderlyingClosedByUio(readUio, true);
    BSL_UIO_Free(readUio);

    return HITLS_APP_SUCCESS;
}

int32_t DecodePemToAsn1(BSL_ASN1_Buffer *buffer) {
    if (buffer == NULL || buffer->buff == NULL || buffer->len == 0) {
        AppPrintError("input is null");
        return BSL_NULL_INPUT;
    }

    BSL_PEM_Symbol symbol;
    char *type = NULL;
    int32_t ret = BSL_PEM_GetSymbolAndType((char *)buffer->buff, buffer->len, &symbol, &type);
    if (ret != BSL_SUCCESS) {
        return ret;
    }

    char *encode = (char *)buffer->buff;
    uint32_t encodeLen = buffer->len;
    uint8_t *binaryData = NULL;
    uint32_t binaryLen = 0;

    ret = BSL_PEM_DecodePemToAsn1(&encode, &encodeLen, &symbol, &binaryData, &binaryLen);
    if (ret != BSL_SUCCESS) {
        return ret;
    }

    BSL_SAL_Free(buffer->buff);
    buffer->buff = binaryData;
    buffer->len = binaryLen;

    return HITLS_APP_SUCCESS;
}

int32_t DecodeBase64ToAsn1(BSL_ASN1_Buffer *buffer) {
    if (buffer == NULL || buffer->buff == NULL || buffer->len == 0) {
        AppPrintError("Invalid buffer (NULL or empty)\n");
        return HITLS_APP_INVALID_ARG;
    }

    uint32_t requiredBinLen = BSL_BASE64_DEC_ENOUGH_LEN(buffer->len);
    uint8_t *binaryData = (uint8_t *)BSL_SAL_Malloc(requiredBinLen);
    if (binaryData == NULL) {
        AppPrintError("Memory allocation failed for binary data\n");
        return HITLS_APP_MEM_ALLOC_FAIL;
    }

    uint32_t actualBinLen = requiredBinLen;
    int32_t ret = BSL_BASE64_Decode((const char *)buffer->buff,
                                    buffer->len,
                                    binaryData,
                                    &actualBinLen
    );

    if (ret != BSL_SUCCESS) {
        AppPrintError("Base64 decode failed\n");
        BSL_SAL_FREE(binaryData);
        return HITLS_APP_DECODE_FAIL;
    }

    BSL_SAL_FREE(buffer->buff);
    buffer->buff = binaryData;
    buffer->len = actualBinLen;

    return HITLS_APP_SUCCESS;
}
